package com.tiancheng.trade.order.util.ums;

import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.*;

/**
 * @Author: likailun
 * @Description: 银联工具类
 * @Date: create in 2021/8/23 6:13 下午
 */
@Slf4j
public class UmsAcpService {
    
    /**
     * 请求报文签名(使用配置文件中配置的私钥证书或者对称密钥签名)<br>
     * 功能：对请求报文进行签名,并计算赋值certid,signature字段并返回<br>
     * @param data 请求报文map<br>
     * @param config 银联相关配置<br>
     * @return　签名后的map对象<br>
     */
    public static Map<String, String> sign(Map<String, String> data, UmsBindingBankCardConfig config) {
        data = filterBlank(data);
        String signMethod = config.getSignMethod();
        String version = UmsConstant.VERSION;

        // 设置签名证书序列号
        data.put(UmsConstant.PARAM_CERTID, CertUtil.getSignCertId());
        // 将Map信息转换成key1=value1&key2=value2的形式
        String stringData = coverMap2String(data);
        byte[] byteSign = null;
        String stringSign = null;
        try {
            // 通过SHA1进行摘要并转16进制
            byte[] signDigest = SecureUtil
                    .sha1X16(stringData, UmsConstant.ENCODING);
            byteSign = SecureUtil.base64Encode(SecureUtil.signBySoft(
                    CertUtil.getSignCertPrivateKey(), signDigest));
            stringSign = new String(byteSign);
            // 设置签名域值
            data.put(UmsConstant.PARAM_SIGNATURE, stringSign);
            return data;
        } catch (Exception e) {
            log.error("签名失败", e);
            return null;
        }
    }


    /**
     * 敏感信息解密，使用配置文件acp_sdk.properties解密<br>
     * @param base64EncryptedInfo 加密信息<br>
     * @param encoding<br>
     * @return 解密后的明文<br>
     */
    public static String decryptData(String base64EncryptedInfo, String encoding) {
        return SecureUtil.decryptData(base64EncryptedInfo, encoding, CertUtil
                .getSignCertPrivateKey());
    }

    /**
     * 敏感信息解密,通过传入的私钥解密<br>
     * @param base64EncryptedInfo 加密信息<br>
     * @param certPath 私钥文件（带全路径）<br>
     * @param certPwd 私钥密码<br>
     * @param encoding<br>
     * @return
     */
    public static String decryptData(String base64EncryptedInfo, String certPath,
                                     String certPwd, String encoding) {
        return SecureUtil.decryptData(base64EncryptedInfo, encoding, CertUtil
                .getSignCertPrivateKeyByStoreMap(certPath, certPwd));
    }


    /**
     * 验证签名(SHA-1摘要算法)<br>
     * @param resData 返回报文数据<br>
     * @param config 银联的<br>
     * @return true 通过 false 未通过<br>
     */
    public static boolean validate(Map<String, String> resData, UmsBindingBankCardConfig config) {
        String stringSign = resData.get(UmsConstant.PARAM_SIGNATURE);
        // 从返回报文中获取certId ，然后去证书静态Map中查询对应验签证书对象
        String certId = resData.get(UmsConstant.PARAM_CERTID);
        // 将Map信息转换成key1=value1&key2=value2的形式
        String stringData = coverMap2String(resData);
        try {
            // 验证签名需要用银联发给商户的公钥证书.
            return SecureUtil.validateSignBySoft(CertUtil
                            .getValidatePublicKey(certId), SecureUtil
                            .base64Decode(stringSign.getBytes(UmsConstant.ENCODING)),
                    SecureUtil.sha1X16(stringData, UmsConstant.ENCODING));
        } catch (UnsupportedEncodingException e) {
            log.error(e.getMessage(), e);
            log.error("验证签名失败", e);
        } catch (Exception e) {
            log.error("验证签名失败", e);
        }
        return false;
    }

    /**
     * 解析返回报文（后台通知）中的customerInfo域：<br>
     * 解base64,如果带敏感信息加密 encryptedInfo 则将其解密并将 encryptedInfo中的域放到customerInfoMap返回<br>
     * @param customerInfo<br>
     * @param encoding<br>
     * @return
     */
    public static Map<String,String> parseCustomerInfo(String customerInfo,String encoding){
        Map<String,String> customerInfoMap = null;
        try {
            byte[] b = SecureUtil.base64Decode(customerInfo.getBytes(encoding));
            String customerInfoNoBase64 = new String(b,encoding);
            //去掉前后的{}
            customerInfoNoBase64 = customerInfoNoBase64.substring(1, customerInfoNoBase64.length()-1);
            customerInfoMap = parseQString(customerInfoNoBase64);
            if(customerInfoMap.containsKey("encryptedInfo")){
                String encInfoStr = customerInfoMap.get("encryptedInfo");
                customerInfoMap.remove("encryptedInfo");
                String encryptedInfoStr = decryptData(encInfoStr, encoding);
                Map<String,String> encryptedInfoMap = parseQString(encryptedInfoStr);
                customerInfoMap.putAll(encryptedInfoMap);
            }
        } catch (UnsupportedEncodingException e) {
            log.error(e.getMessage(), e);
        } catch (IOException e) {
            log.error(e.getMessage(), e);
        }
        return customerInfoMap;
    }

    /**
     * 过滤请求报文中的空字符串或者空字符串
     * @param contentData
     * @return
     */
    public static Map<String, String> filterBlank(Map<String, String> contentData){
        Map<String, String> submitFromData = new HashMap<String, String>();
        Set<String> keyset = contentData.keySet();

        for(String key:keyset){
            String value = contentData.get(key);
            if (value != null && !"".equals(value.trim())) {
                // 对value值进行去除前后空处理
                submitFromData.put(key, value.trim());
            }
        }
        return submitFromData;
    }


    /**
     * 将Map中的数据转换成key1=value1&key2=value2的形式 不包含签名域signature
     *
     * @param data
     *            待拼接的Map数据
     * @return 拼接好后的字符串
     */
    public static String coverMap2String(Map<String, String> data) {
        TreeMap<String, String> tree = new TreeMap<String, String>();
        Iterator<Map.Entry<String, String>> it = data.entrySet().iterator();
        while (it.hasNext()) {
            Map.Entry<String, String> en = it.next();
            if (UmsConstant.PARAM_SIGNATURE.equals(en.getKey().trim())) {
                continue;
            }
            tree.put(en.getKey(), en.getValue());
        }
        it = tree.entrySet().iterator();
        StringBuffer sf = new StringBuffer();
        while (it.hasNext()) {
            Map.Entry<String, String> en = it.next();
            sf.append(en.getKey() + UmsConstant.EQUAL + en.getValue()
                    + UmsConstant.AMPERSAND);
        }
        return sf.substring(0, sf.length() - 1);
    }

    /**
     * 解析应答字符串，生成应答要素
     *
     * @param str
     *            需要解析的字符串
     * @return 解析的结果map
     * @throws UnsupportedEncodingException
     */
    public static Map<String, String> parseQString(String str) {

        Map<String, String> map = new HashMap<String, String>();
        int len = str.length();
        StringBuilder temp = new StringBuilder();
        char curChar;
        String key = null;
        boolean isKey = true;
        boolean isOpen = false;//值里有嵌套
        char openName = 0;
        if(len>0){
            for (int i = 0; i < len; i++) {// 遍历整个带解析的字符串
                curChar = str.charAt(i);// 取当前字符
                if (isKey) {// 如果当前生成的是key

                    if (curChar == '=') {// 如果读取到=分隔符
                        key = temp.toString();
                        temp.setLength(0);
                        isKey = false;
                    } else {
                        temp.append(curChar);
                    }
                } else  {// 如果当前生成的是value
                    if(isOpen){
                        if(curChar == openName){
                            isOpen = false;
                        }

                    }else{//如果没开启嵌套
                        if(curChar == '{'){//如果碰到，就开启嵌套
                            isOpen = true;
                            openName ='}';
                        }
                        if(curChar == '['){
                            isOpen = true;
                            openName =']';
                        }
                    }

                    if (curChar == '&' && !isOpen) {// 如果读取到&分割符,同时这个分割符不是值域，这时将map里添加
                        putKeyValueToMap(temp, isKey, key, map);
                        temp.setLength(0);
                        isKey = true;
                    } else {
                        temp.append(curChar);
                    }
                }

            }
            putKeyValueToMap(temp, isKey, key, map);
        }
        return map;
    }
    private static void putKeyValueToMap(StringBuilder temp, boolean isKey,
                                         String key, Map<String, String> map) {
        if (isKey) {
            key = temp.toString();
            if (key.length() == 0) {
                throw new RuntimeException("QString format illegal");
            }
            map.put(key, "");
        } else {
            if (key.length() == 0) {
                throw new RuntimeException("QString format illegal");
            }
            map.put(key, temp.toString());
        }
    }


    /**
     * 将形如key=value&key=value的字符串转换为相应的Map对象
     *
     * @param result
     * @return
     */
    public static Map<String, String> convertResultStringToMap(String result) {
        Map<String, String> map = null;

        if (result != null && !"".equals(result.trim())) {
            if (result.startsWith("{") && result.endsWith("}")) {
                result = result.substring(1, result.length() - 1);
            }
            map = parseQString(result);
        }

        return map;
    }


    /**
     * 获取敏感信息加密证书的物理序列号<br>
     * @return
     */
    public static String getEncryptCertId(){
        return CertUtil.getEncryptCertId();
    }


    /**
     * 获取请求参数中所有的信息
     * 当商户上送frontUrl或backUrl地址中带有参数信息的时候，
     * 这种方式会将url地址中的参数读到map中，会导多出来这些信息从而致验签失败，这个时候可以自行修改过滤掉url中的参数或者使用getAllRequestParamStream方法。
     * @param request
     * @return
     */
    public static Map<String, String> getAllRequestParam(
            final HttpServletRequest request) {
        Map<String, String> res = new HashMap<String, String>();
        Enumeration<?> temp = request.getParameterNames();
        if (null != temp) {
            while (temp.hasMoreElements()) {
                String en = (String) temp.nextElement();
                String value = request.getParameter(en);
                res.put(en, value);
                // 在报文上送时，如果字段的值为空，则不上送<下面的处理为在获取所有参数数据时，判断若值为空，则删除这个字段>
                if (res.get(en) == null || "".equals(res.get(en))) {
                    // System.out.println("======为空的字段名===="+en);
                    res.remove(en);
                }
            }
        }
        return res;
    }

    /**
     * 组装收款方信息(接入机构配置了敏感信息加密)
     * @param encoding 编码方式
     * @return 用{}连接并base64后的收款方信息
     */
    public static String getPayeeInfoWithEncrpyt(Map<String, String> payeeInfoMap,String encoding) {
        return formInfoBase64WithEncrpyt(payeeInfoMap,encoding);
    }

    /**
     * 用{}连接并base64(接入机构配置了敏感信息加密)
     * @param map
     * @param encoding
     * @return
     */
    public static String formInfoBase64WithEncrpyt(Map<String, String> map,String encoding){
        StringBuffer sf = new StringBuffer();
        String info = sf.append(UmsConstant.LEFT_BRACE).append(coverMap2String(map)).append(UmsConstant.RIGHT_BRACE).toString();
        info = SecureUtil.encryptData(info, encoding, CertUtil.getEncryptCertPublicKey());
        return info;
    }

    /**
     * 解析返回报文中的payeeInfo域，敏感信息加密时使用：<br>
     * @param payeeInfo<br>
     * @param encoding<br>
     * @return
     */
    public static Map<String, String> parsePayeeInfoEnc(String payeeInfo, String encoding){
        return parsePayerInfoEnc(payeeInfo, encoding);
    }
    /**
     * 解析返回报文的payerInfo域，敏感信息加密时使用：<br>
     * @param payerInfo<br>
     * @param encoding<br>
     * @return
     */
    public static Map<String, String> parsePayerInfoEnc(String payerInfo, String encoding){
        payerInfo = decryptData(payerInfo, encoding);
        Map<String, String> payerInfoMap;
        payerInfoMap = convertResultStringToMap(payerInfo);
        return payerInfoMap;
    }

    /**
     * 解析返回报文中的payerInfo域，敏感信息加密时使用，多证书方式。<br>
     * @param payerInfo<br>
     * @param encoding<br>
     * @return
     */
    public static Map<String, String> parsePayerInfoEnc(String payerInfo, String certPath,
                                                        String certPwd, String encoding){
        payerInfo = decryptData(payerInfo, certPath, certPwd, encoding);
        Map<String, String> payerInfoMap;
        payerInfoMap = convertResultStringToMap(payerInfo);
        return payerInfoMap;
    }
}
